/*
 * Copyright (c) 2024 endless-sky
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

// Copyright (c) 2024 Endless Inc.
#ifndef FOUNDATION_MUXER_INCLUDE_PCM_AAC_ENCODER_H_
#define FOUNDATION_MUXER_INCLUDE_PCM_AAC_ENCODER_H_

#include <cstdint>
#include <functional>
#include <memory>
#include <string>
#include <vector>

#include <gst/gst.h>
#include <gst/app/gstappsrc.h>
#include <gst/app/gstappsink.h>

namespace El {
namespace Muxer {

/**
 * @brief PCM to AAC encoder.
 *
 * This class processes raw PCM audio data incrementally and encodes it to AAC.
 * Feed PCM data chunks via ProcessInput and retrieve AAC data using GetData.
 * Output AAC data is in ADTS format which can be played independently.
 */
class PcmAacEncoder {
public:
    /**
     * @brief PCM sample format enumeration.
     */
    enum class PcmFormat {
        S16LE, // 16-bit signed little-endian (most common)
        S32LE, // 32-bit signed little-endian
        FLT    // 32-bit float
    };

    /**
     * @brief Constructor.
     */
    PcmAacEncoder();

    /**
     * @brief Destructor. Cleans up FFmpeg resources.
     */
    ~PcmAacEncoder();

    // Disable copy and move semantics
    PcmAacEncoder(const PcmAacEncoder &) = delete;
    PcmAacEncoder &operator=(const PcmAacEncoder &) = delete;
    PcmAacEncoder(PcmAacEncoder &&) = delete;
    PcmAacEncoder &operator=(PcmAacEncoder &&) = delete;

    /**
     * @brief Initializes the converter. Must be called before processing data.
     *
     * @param sample_rate Sample rate of the output AAC stream (e.g., 8000, 16000, 48000).
     * @param channels Number of channels in the output AAC stream (e.g., 1 or 2).
     * @param pcm_format Format of the input PCM samples (S16LE, S32LE, or FLT).
     * @param bitrate Target bitrate for AAC encoding in bits per second (e.g., 128000).
     * @param input_sample_rate Sample rate of the input PCM stream. If different from sample_rate, resampling will be
     * enabled. Default: 0 (same as output).
     * @param input_channels Number of channels in the input PCM stream. If different from channels, channel mixing will
     * be enabled. Default: 0 (same as output).
     * @return true if initialization is successful, false otherwise.
     */
    bool Initialize(int sample_rate,
                    int channels,
                    PcmFormat pcm_format = PcmFormat::S16LE,
                    int bitrate = 128000,
                    int input_sample_rate = 0,
                    int input_channels = 0);

    /**
     * @brief Processes a chunk of input PCM data.
     *
     * The input data should be raw PCM samples in the format specified during initialization.
     * For interleaved stereo S16LE: [L0, R0, L1, R1, ...]
     * Call GetData() after this to retrieve encoded AAC packets.
     *
     * @param data Pointer to the input PCM data.
     * @param size Size of the input data chunk in bytes.
     * @return true if processing is successful, false on error.
     */
    bool ProcessInput(const uint8_t *data, size_t size);

    /**
     * @brief Retrieves available encoded AAC data.
     *
     * This method should be called after ProcessInput() to get the encoded AAC packets.
     * When called with flush=true, it will also flush internal buffers to get remaining data.
     *
     * @param buffer Vector to store the AAC data in ADTS format. Will be cleared and filled with new data.
     * @param flush If true, flush internal buffers to get all remaining data (call once at end of stream).
     * @return true if successful (buffer may be empty if no data available), false on error.
     */
    bool GetData(std::vector<uint8_t> &buffer, bool flush = false);

    /**
     * @brief Fast reset the encoder after a flush (EOS).
     *
     * Transitions the pipeline through READY -> PAUSED -> PLAYING to clear EOS
     * and resets internal flags so the same pipeline can continue to accept input
     * without rebuilding.
     *
     * @return true on success, false otherwise.
     */
    bool Reset();

private:
    // Private helper methods for encoding logic
    bool BuildPipeline(int sample_rate, int channels, PcmFormat pcm_format, int bitrate);
    bool PushBuffer(const uint8_t *data, size_t size);
    bool PullSamples(std::vector<uint8_t> &out, bool drain);
    const char *GetGstSampleFormat(PcmFormat format) const;
    GstCaps *CreateInputCaps(int sample_rate, int channels, PcmFormat format) const;

    GstElement *pipeline_ = nullptr;
    GstElement *appsrc_ = nullptr;
    GstElement *appsink_ = nullptr;

    // Configuration
    int sample_rate_ = 0;       // Output sample rate
    int channels_ = 0;          // Output channels
    int input_sample_rate_ = 0; // Input sample rate (0 = same as output)
    int input_channels_ = 0;    // Input channels (0 = same as output)
    PcmFormat pcm_format_ = PcmFormat::S16LE;
    int input_bytes_per_sample_ = 0;
    int64_t samples_pushed_ = 0;

    bool initialized_ = false;
    bool encoder_flushed_ = false;
    bool eos_pushed_ = false;
};

} // namespace Muxer
} // namespace El

#endif // FOUNDATION_MUXER_INCLUDE_PCM_AAC_ENCODER_H_
